import threading
import logging
import storage_exception as se
import misc

class LockType:
    shared = "shared"
    exclusive = "exclusive"

    @classmethod
    def validate(cls, ltype):
        if type(getattr(cls, str(ltype), None)) != str:
            raise ValueError("invalid lock type '%s'" % str(ltype))
    def __init__(self, ltype = None):
        if not ltype:
            raise Exception("LockType: pure struct")
        self.validate(ltype)
        self.value = ltype
    @classmethod
    def fromState(self, lockstate):
        if lockstate == LockState.shared:
            return self.shared
        elif lockstate == LockState.locked:
            return self.exclusive
        raise ValueError("invalid lockstate %s" % str(lockstate))
    def __str__(self):
        return str(self.value)

class LockState:
    free = "free"
    shared = "shared"
    locked = "locked"

    def __init__(self, state=free):
        self.validate(state)
        self.state = state

    def __str__(self):
        return self.state

    def __eq__(self, x):
        if type(x) == str:
            return self.state == x
        if isinstance(x, self):
            return x.state == self.state

    def __ne__(self, x):
        return not self.__eq__(x)

    @classmethod
    def fromType(self, locktype):
        if str(locktype) == LockType.shared:
            return self.shared
        if str(locktype) == LockType.exclusive:
            return self.locked
        raise ValueError("invalid locktype %s" % str(locktype))
    @classmethod
    def validate(cls, state):
        try:
            if type(getattr(cls, state)) != str:
                raise ValueError
        except:
            raise ValueError("invalid lock state %s" % str(state))

class Namespace(object):
    namespaceslock = threading.Lock()
    namespaces = {}
    log = logging.getLogger('irs')

    @classmethod
    def _addSpace(cls, space):
        cls.namespaceslock.acquire()
        try:
            if space.name in cls.namespaces:
                raise ValueError("space name '%s' must be unique" % space.name)
            cls.namespaces[space.name] = space
        finally:
            cls.namespaceslock.release()

    @classmethod
    def _rmSpace(cls, space):
        cls.namespaceslock.acquire()
        try:
            if not space.name in cls.namespaces:
                cls.log.warning("Namespace._rmSpace: no such space '%s'", space.name)
                return
            del cls.namespaces[space.name]
        finally:
            cls.namespaceslock.release()

    @classmethod
    def listNamespaces(cls):
        cls.namespaceslock.acquire()
        try:
            return cls.namespaces.keys()
        finally:
            cls.namespaceslock.release()

    @classmethod
    def getNamespace(cls, name):
        cls.namespaceslock.acquire()
        try:
            if not name in cls.namespaces:
                raise ValueError("no such space '%s'" % name)
            return cls.namespaces[name]
        finally:
            cls.namespaceslock.release()

    @classmethod
    def removeNamespace(cls, name):
        ns = cls.getNamespace(name)
        if ns.listResources():
            raise ValueError("namespace not empty")
        cls._rmSpace(ns)

    def __init__(self, name):
        self.name = name
        self.resources = {}
        self.lock = threading.Lock()
        self._addSpace(self)

    def _addResource(self, resource):
        self.lock.acquire()
        try:
            if resource.name in self.resources:
                raise ValueError("resource %s name already exists in %s" % (str(resource), str(self)))
            self.resources[resource.name] = resource
        finally:
            self.lock.release()

    def _removeResource(self, resource):
        self.lock.acquire()
        try:
            if resource.name not in self.resources:
                #self.log.warning("resource %s not in namespace %s", str(resource), str(self))
                return
            del self.resources[resource.name]
        finally:
            self.lock.release()

    def listResources(self):
        self.lock.acquire()
        try:
            return self.resources.values()
        finally:
            self.lock.release()

    def getResource(self, name):
        """
        Get the named resource, allocate new resource if not already exists.
        """
        self.lock.acquire()
        try:
            resource = self.resources.get(name, None)
            if resource:
                ref = resource.incref()
                self.log.debug("resource %s after incref ref %s", str(resource), ref)
                return resource
            resource = Resource(name, self)
            self.resources[resource.name] = resource
            return resource
        finally:
            self.lock.release()

    def freeResource(self, name):
        """
        Signal that the resource is not needed anymore. The resource will not
        be deleted until the last holder is freeing it.
        """
        self.lock.acquire()
        try:
            resource = self.resources.get(name, None)
            if not resource:
                raise ValueError("resource %s not found in %s" % (name, str(self)))
            ref = resource.decref()
            self.log.debug("resource %s after decref ref %s", str(resource), ref)
            if ref < 0:
                self.log.error("Namespace %s: resource %s ref %s!!!", str(self), str(resource))
            if ref <= 0:
                resource._hasBeenFreed = True
                del self.resources[resource.name]
        finally:
            self.lock.release()

    def __str__(self):
        return self.name

class Resource(object):
    """
    Resource = model an arbitrary resoruce.
    Resources are managed via the namespace object so please do not create
    resource objects directly unless you really know what you are doing.
    """
    log = logging.getLogger('irs')
    fields = {
        "name": str,
        "namespace": str,
        "lockstate": LockState,
    }
    def __init__(self, name, space, data = None):
        self.name = name
        self.space = space
        self.namespace = str(self.space)
        self.owners = [] # free if empty, single item if exclusive, 1 or more if shared
        self.lockstate = LockState()
        self.requests = []
        self.lock = threading.Lock()
        self.refcount = 1
        self._data = data
        self._hasBeenFreed = False

    def __del__(self):
        if not self._hasBeenFreed:
            self._hasBeenFreed = True
            def free(logger, namespace):
                self.log.warn("Resource %s was not released manually. Releasing automatically." % self.name)
                self.space._removeResource(self)

            #threading.Thread(target=free).start()

    def incref(self):
        self.lock.acquire()
        self.refcount += 1
        ref = self.refcount
        self.lock.release()
        return ref

    def decref(self):
        self.lock.acquire()
        self.refcount -= 1
        ref = self.refcount
        self.lock.release()
        return ref

    def __granted(self, requester, lockstate):
        """ Call under self.lock """
        self.owners.append(requester)
        self.lockstate = lockstate
        self.log.debug("Resource '%s': __granted %s to '%s'", str(self), str(lockstate), str(requester))
        return True

    def __wait(self, requester, locktype, timeout_ms):
        """ Call under self.lock """
        self.log.debug("Resource %s - %s wait for %s access %s ms", str(self), str(requester), str(locktype), timeout_ms)
        req = Request(self, requester, locktype)
        self.requests.append(req)
        # note that _registered call must be done under self.lock to prevent race with release
        requester._registered(req)
        self.lock.release()
        req.wait(timeout_ms)
        self.lock.acquire()
        acquired = requester in self.owners

        # We might have the resource but the request object timed out before completing its run
        # We wait to make sure this all happens in the right order.
        if acquired:
            req.wait()

        if not acquired and req in self.requests:
            # we probably timedout - remove the req and cancel it
            self.requests.remove(req)
            requester._canceled(req)
        self.log.debug("Resource %s - %s acquired %s", str(self), str(requester), acquired)
        return acquired

    def __register(self, requester, locktype, request):
        """ Call under self.lock """
        if requester in self.owners:
            raise ValueError("requester '%s' already owns resource '%s'" % (str(requester), str(self)))
        for r in self.requests:
            if r.requester == requester:
                raise ValueError("requster '%s' already requested resource '%s'" % (str(requester), str(self)))
        self.requests.append(request)
        # note that _registered call must be done under self.lock to prevent race with release
        requester._registered(request)
        if self.lockstate == LockState.free:
            return self.__granted(requester, LockState.fromType(locktype))

        # We should register for async grant if the lock is not free and:
        # 1. we want exclusive access, or
        # 2. the currect owner is exclusive or
        # 3. there are pending requests (note that requests will always contain "request" in this stage.
        # The last check is to avoid starving exclusive lockers by shared
        # lockers joining existing shared owner.
        if locktype == LockType.exclusive or self.lockstate == LockState.locked or len(self.requests) > 1:
            return False

        # locktype is shared and there is no other requests - join the lock owners
        return self.__granted(requester, LockState.shared)

    def acquire(self, requester, locktype, timeout_ms):
        """
        Synchronously acquire the resource, optionally with timeout
        Return true if resource is acquired, False if not (timed-out).
        """
        Owner.validate(requester)
        LockType.validate(locktype)
        self.log.debug("Resource %s: %s acquire %s (%s)", str(self), str(requester), str(locktype), timeout_ms)
        self.lock.acquire()
        wait = False
        try:
            if requester in self.owners:
                raise ValueError("requester '%s' already owns resource '%s'" % (str(requester), self.name))
            self.log.debug("Resource %s - lockstate %s", str(self), str(self.lockstate))
            if self.lockstate == LockState.free:
                return self.__granted(requester, LockState.fromType(locktype))

            # We should wait if:
            # 1. we want exclusive access, or
            # 2. the currect owner is exclusive or
            # 3. there are pending requests.
            # The last check is to avoid starving exclusive lockers by shared
            # lockers joining existing shared owner.
            if locktype == LockType.exclusive or self.lockstate == LockState.locked or self.requests:
                wait = True
                return self.__wait(requester, locktype, timeout_ms)

            # locktype is shared and there are no other requests
            return self.__granted(requester, LockState.shared)
        finally:
            self.lock.release()
            # if we waited then the releaser would call the callback,
            # but if not we have to do it ourselves:
            if not wait:
                try:
                    requester._acquired(self)
                except Exception, e:
                    misc.logException(e)
                    try:
                        self.release(requester)
                    except Exception, e:
                        self.log.error("exception while try to recover from bad _acquired")
                        misc.logException(e)
                        pass

    def release(self, owner):
        """
        Release the resource and assign new owner if applicable.
        """
        Owner.validate(owner)
        self.log.debug("Resource %s: %s releasing", str(self), str(owner))
        self.lock.acquire()
        granted = []
        try:
            if owner not in self.owners:
                raise ValueError("%s not owner of resource %s" % (owner, self.name))
            self.owners.remove(owner)
            self.log.debug("Resource %s: owners after release %s", str(self), [ str(o) for o in self.owners ])
            if self.owners:
                return
            self.log.debug("Resource %s: requests after release %s", str(self), [ str(r) for r in self.requests ])
            while self.requests:
                req = self.requests.pop(0)
                if req.canceled():
                    continue
                if req.locktype == LockType.shared:
                    granted.append(req)
                    self.__granted(req.requester, LockState.shared)
                    continue
                # exclusive request
                if granted:
                    # shared granted exists - just stop and return ex req to queue
                    self.requests.insert(0, req)
                    return
                # exclusive request granted
                granted.append(req)
                return self.__granted(req.requester, LockState.locked)
            if granted:
                return  # granted for shared, bail out
            self.log.debug("Resource %s: free lock", str(self))
            # no more requesters, just free the resource
            self.lockstate = LockState.free
            return False
        finally:
            self.lock.release()
            owner._released(self)
            for req in granted:
                self.log.debug("Resource %s: grant lock to req %s", str(self), str(req))
                req.grant()

    def register(self, requester, locktype):
        """
        Register to own this resource.
        """
        Owner.validate(requester)
        LockType.validate(locktype)
        self.log.debug("Resource %s: %s register %s", str(self), str(requester), str(locktype))
        req = Request(self, requester, locktype)
        self.lock.acquire()
        granted = False
        try:
            granted = self.__register(requester, locktype, req)
        finally:
            self.log.debug("Resource.resister %s: %s - granted %s", str(self), str(requester), granted)
            if granted and req in self.requests:
                self.requests.remove(req)
            self.lock.release()
        if granted:
            req.grant()
        return req

    def cancel(self, request):
        self.log.debug("Resource %s: cancel request %s", str(self), str(request))
        self.lock.acquire()
        try:
            if request not in self.requests:
                self.log.warning("Resource %s: request %s can't be canceled - not registered", str(self), str(request))
                return
            self.requests.remove(request)
        finally:
            self.lock.release()
        request.cancel()

    def report(self):
        return "%s: status %s owners %s requests %s" % (str(self), str(self.lockstate),
                [str(o) for o in self.owners],
                [(str(r.requester), str(r.locktype)) for r in self.requests])

    def data(self, owner):
        self.log.debug("Resource %s: data requested by %s", str(self), str(owner))
        self.lock.acquire()
        try:
            if owner in self.owners:
                return self._data
        finally:
            self.lock.release()

    def setData(self, owner, data, olddata):
        self.log.debug("Resource %s: data requested by %s", str(self), str(owner))
        self.lock.acquire()
        try:
            if not owner in self.owners:
                raise ValueError("%s: SetData: %s not owner" % (str(self), str(owner)))
            if self._data != olddata:
                raise ValueError("%s: SetDtat: %s attempts to set data, but old data %s do not match %s" % (str(self, str(owner), str(olddata), str(data))))
            self._data = data
        finally:
            self.lock.release()

    def __str__(self):
        #return "%s: %s/%s" % (repr(self), self.namespace, self.name)
        return "%s/%s" % (self.namespace, self.name)

class Request(object):
    """
    Internal request object, don't use directly
    """
    log = logging.getLogger('irs')

    def __init__(self, resource, requester, locktype):
        self.resource = resource
        self.requester = requester
        self.locktype = locktype
        self.event = threading.Event()
        self._canceled = False
    def wait(self, timeout_ms = None):
        to = None
        if type(timeout_ms) == int:
            to = float(timeout_ms/1000)
        self.event.wait(to)
        return self.granted()
    def cancel(self):
        self.log.debug("Request.cancel: %s", str(self))
        self._canceled = True
        self.requester._canceled(self)
        if not self.event.isSet():
            self.event.set()
    def canceled(self):
        return self._canceled
    def grant(self):
        self.log.debug("Request.grant: %s", str(self))
        if self._canceled:
            self.log.debug("Request.grant: %s, race with cancel, release", str(self))
            self.resource.release(self.requester)
            return
        self.requester._granted(self)
        self.event.set()
    def granted(self):
        return not self._canceled and self.event.isSet()
    def status(self):
        if self._canceled:
            return "canceled"
        if self.event.isSet():
            return "granted"
        return "waiting"
    def __str__(self):
        return "%s: request for %s - %s: %s" % (str(self.requester), str(self.resource), str(self.locktype), self.status())

class Owner(object):
    log = logging.getLogger('irs')

    def __init__(self, ownerobject, raiseonfailure = False):
        self.ownerobject = ownerobject
        self.requests = []
        self.resources = []
        self.lock = threading.Lock()
        self.raiseonfailure = raiseonfailure

    def _released(self, resource):
        """ internal callback used by Resource """
        if not isinstance(resource, Resource):
            raise TypeError("%s is not resource" % str(resource))
        self.log.debug("Owner %s: _released %s/%s (%s)", str(self), str(resource), repr(resource), [ "%s/%s" % (str(r), repr(r)) for r in self.resources])
        ns = None
        name = None
        self.lock.acquire()
        try:
            if not resource in self.resources:
                raise ValueError("resoure %s not owned by %s" % (str(resource), str(self)))
            ns = resource.namespace
            name = resource.name
            self.resources.remove(resource)
        finally:
            self.lock.release()
            if hasattr(self.ownerobject, "resourceReleased") and ns and name:
                self.ownerobject.resourceReleased(ns, name)

    def _acquired(self, resource):
        """ internal callback used by Request:
            Resource is immediately acquired
        """
        if not isinstance(resource, Resource):
            raise TypeError("%s is not resource" % str(resource))
        self.log.debug("Owner %s: _acquired %s", str(self), str(resource))
        ns = None
        name = None
        self.lock.acquire()
        try:
            if resource in self.resources:
                raise ValueError("%s is already acquired by %s" % (str(resource), str(self)))
            ns = resource.namespace
            name = resource.name
            lockstate = resource.lockstate
            self.resources.append(resource)
        finally:
            self.lock.release()
            if hasattr(self.ownerobject, "resourceAcquired") and ns and name:
                self.ownerobject.resourceAcquired(ns, name, LockType.fromState(lockstate))

    def _granted(self, request):
        """ internal callback used by Request
            Resource is asynchronously granted or granted after waiting
        """
        if not isinstance(request, Request):
            raise TypeError("%s is not request" % str(request))
        self.log.debug("Owner %s: req _granted %s", str(self), str(request))
        ns = None
        name = None
        self.lock.acquire()
        try:
            if not request in self.requests:
                self.log.warning("request %s not requested by %s" %  (str(request), str(self)))
                return
            self.requests.remove(request)
            if request.resource in self.resources:
                raise ValueError("%s is already acquired by %s" % (str(request.resource), str(self)))
            ns = request.resource.namespace
            name = request.resource.name
            locktype = request.locktype
            self.resources.append(request.resource)
        finally:
            self.lock.release()
            if hasattr(self.ownerobject, "resourceAcquired") and ns and name:
                self.ownerobject.resourceAcquired(ns, name, locktype)

    def _canceled(self, request):
        """ internal callback used by Request.
            May be called undel resource lock, so pay attention.
        """
        if not isinstance(request, Request):
            raise TypeError("%s is not request" % str(request))
        self.log.debug("Owner %s: req _canceled %s", str(self), str(request))
        self.lock.acquire()
        try:
            if not request in self.requests:
                self.log.warning("request %s not requested by %s" % (str(request), str(self)))
                return
            self.requests.remove(request)
        finally:
            self.lock.release()

    def _registered(self, request):
        """ internal callback used by Resource
            This callback is called under resource.lock - take care.
        """
        if not isinstance(request, Request):
            raise TypeError("%s is not request" % str(request))
        ns = None
        name = None
        self.log.debug("Owner %s: req _registered %s", str(self), str(request))
        self.lock.acquire()
        try:
            if request in self.requests:
                raise ValueError("request %s is already requested by %s" % (str(request), str(self)))
            ns = request.resource.namespace
            name = request.resource.name
            locktype = request.locktype
            self.requests.append(request)
        finally:
            self.lock.release()
            if hasattr(self.ownerobject, "resourceRegistered") and ns and name:
                self.ownerobject.resourceRegistered(ns, name, locktype)

    def release(self, namespace, name):
        for r in self.resources[:]:
            if r.namespace == namespace and r.name == name:
                r.release(self)
                r.space.freeResource(r.name)
                #self.log.debug("Owner %s: resource %s", str(self), r.report())
                return True
        raise ValueError("Owner: '%s' can't release %s.%s: not acquired" % (str(self), namespace, name))

    def acquire(self, namespace, name, locktype, timeout_ms, raiseonfailure=None):
        if raiseonfailure is None:
            raiseonfailure = self.raiseonfailure
        space = Namespace.getNamespace(namespace)
        resource = space.getResource(name)
        if resource in self.resources:
            raise ValueError("Owner %s: acquire: resource %s is already acquired" % (str(self), str(resource)))
        acquired = resource.acquire(self, locktype, timeout_ms)
        if acquired or not raiseonfailure:
            return acquired
        raise se.ResourceTimeout("%s: resource %s after %s ms" % (str(self), resource, timeout_ms))

    def register(self, namespace, name, locktype):
        space = Namespace.getNamespace(namespace)
        resource = space.getResource(name)
        if resource in self.resources:
            raise ValueError("Owner %s: acquire: resource %s is already acquired" % (str(self), str(resource)))
        resource.register(self, locktype)

    def cancel(self, namespace, name):
        """
        Cancel a pending request. Note that cancel may race with grant and in this case
        it is not ensured that the resource is not owned. In addition it is not allowed to
        release any resources granted due to the above race - the owning thread may be
        not aware of this!
        """
        self.log.debug("Owner.cancel: %s %s" % (namespace,name))
        for req in self.requests[:]:
            if req.resource.name == name and req.resource.namespace == namespace:
                req.resource.cancel(req)
                return True
        return False

    def wait(self, namespace, name, to):
        self.log.debug("Owner.wait: %s %s - %s ms" % (namespace,name,to))
        for req in self.requests[:]:
            if req.resource.name == name and req.resource.namespace == namespace:
                return req.wait(to)
        # req not found - check that it is not granted
        for res in self.resources[:]:
            if res.namespace == namespace and res.name == name:
                return True
        # Note that there is a risk of another thread that is racing with us and
        # releases this resource - but this should be synced above us
        raise ValueError("Owner %s: %s/%s is not requested" % (str(self), namespace, name))

    def getData(self, namespace, name):
        for res in self.resources[:]:
            if res.namespace == namespace and res.name == name:
                return res.data()

    def setData(self, namespace, name, data):
        for res in self.resources[:]:
            if res.namespace == namespace and res.name == name:
                return res.setData(data, None)

    def releaseAll(self):
        self.log.debug("Owner.releaseAll requests %s resources %s" % (self.requests,self.resources))
        for req in self.requests[:]:
            req.resource.cancel(req)
        for res in self.resources[:]:
            res.release(self)
            res.space.freeResource(res.name)

    def cancelAll(self):
        self.log.debug("Owner.cancelAll requests %s" % (self.requests))
        for req in self.requests[:]:
            req.resource.cancel(req)

    def ownedResources(self):
        res = self.resources[:]
        return [ (r.namespace, r.name, r.lockstate) for r in res]

    def requestedResources(self):
        reqs = self.requests[:]
        return [(r.resource.namespace, r.resource.name, r.locktype) for r in reqs]

    def requestsGranted(self):
        return not self.requests

    @classmethod
    def validate(cls, obj):
        return isinstance(obj, cls)

    def __str__(self):
        return str(self.ownerobject)



# Usage example
if __name__ == "__main__":
    import time
    LOG_FILENAME = '/tmp/resource.log'
    logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,format="%(asctime)s - %(threadName)s - %(message)s")

    def negative(func, *args, **kwargs):
        print "# Negative test: %s %s %s" % (str(func), str(args), str(kwargs))
        try:
            func(*args, **kwargs)
            raise LException("func succeeded - should fail")
        except Exception, e:
            print "## failed as expected with ''%s'' - OK" % str(e)
            pass

    class LException(Exception):
        def __init__(self, s=None):
            if s:
                logging.error("**** %s", s)
            Exception.__init__(self, s)

    def create_ns(l):
        print "create namespaces %s" % str(l)
        for ns in l:
            Namespace(ns)

    def list_ns():
        print "# list namespaces"
        l = Namespace.listNamespaces()
        print "namespaces: %s" % l
        return l

    def check_lists(nslist, checklist):
        for i in checklist:
            if i not in nslist:
                raise LException("missing %s from list %s" % (i,nslist))
            if len(nslist) != len(checklist):
                raise LException("unknown memeber in '%s'?" % str(nslist))

    def get_ns(name):
        print "# get namespace '%s'" % name
        ns = Namespace.getNamespace("temp")
        print "temp ns: %s" % str(ns)
        return ns

    def add_res(o, ns, name, ltype, to):
        print "# add resource: %s.%s - %s (%d)" % (ns, name, str(ltype), to)
        granted = o.acquire(ns, name, ltype, to)
        check_granted(o, ns, name, ltype, granted)
        return granted

    def register_res(o, ns, name, ltype):
        print "# register to resource: %s.%s - %s" % (ns, name, str(ltype))
        return o.register(ns, name, ltype)

    def check_not_owning(o, ns, name):
        reslist = o.ownedResources()
        print "# release %s (owns %s)" % (str(o), reslist)
        for r in reslist:
            if r[0] == ns and r[1] == name:
                raise LException("%s still owner of %s/%s after release/not granted" % (str(o), ns, name))

    def release_res(o, ns, name):
        o.release(ns, name)
        check_not_owning(o, ns, name)

    def list_ns_res(ns):
        print "# list the resource in %s" % ns
        reslist = Namespace.getNamespace(ns).listResources()
        resnames = [ r.name for r in reslist ]
        print resnames
        return resnames

    def ns_report(ns):
        print "# Report for namespace %s" % ns
        for r in Namespace.getNamespace(ns).listResources():
            print r.report()

    def checkResourceOwenership(ns, name, owners):
        for o in owners:
            print "owner %s: %s" %(str(o), o.ownedResources())
        l = Namespace.getNamespace(ns).listResources()
        for r in l:
            if r.name != name:
                continue
            for i in owners:
                if i not in r.owners:
                    raise LException("%s not found in %s" % (str(i), str(r)))
            return r
        raise LException("%s.%s not found" % (ns, name))

    def check_granted(o, ns, name, ltype, granted):
        if not granted:
            check_not_owning(o, ns, name)
            return
        r = checkResourceOwenership(ns, name, [o])
        if r.lockstate != LockState.fromType(ltype):
            raise LException("check_granted: r %s lockstate %s do not match requested type %s" % (str(r), r.lockstate, ltype))

    # Test basic Namespace ops
    nslist = ["pools", "domains", "dom-x", "temp"]
    create_ns(nslist)

    l = list_ns()

    check_lists(l, nslist)

    ns = get_ns("temp")

    negative(get_ns, "xxx")

    print "# remove namespace 'temp'"
    Namespace.removeNamespace("temp")
    nslist.remove("temp")
    l = list_ns()
    check_lists(l, nslist)

    print "# get owner"
    o1 = Owner("unit-test-one")
    o2 = Owner("unit-test-two")

    print "# add first resource"
    add_res(o1, "pools", "first", LockType.shared, 0)
    ns_report("pools")

    print "# add some pools"
    pools = ["first"]
    for i in range(3):
        p = "pool-" + str(i) * 3
        pools.append(p)
        add_res(o1, "pools", p, LockType.shared, 0)

    lres = list_ns_res("pools")

    check_lists(lres, pools)

    negative(add_res, o1, "pools", pools[0], LockType.shared, 0)

    print "# add some domains"
    domains = []
    for i in range(5):
        p = "domain-" + str(i) * 3
        domains.append(p)
        add_res(o1, "domains", p, LockType.shared, 0)

    lres = list_ns_res("domains")

    check_lists(lres, domains)

    negative(add_res, o1, "domains", domains[0], LockType.shared, 0)

    print "# try to share some resources"
    for i in range(3):
        p = "domain-" + str(i) * 3
        add_res(o2, "domains", p, LockType.shared, 0)

    ns_report("pools")

    print "# o1 owns %s" % o1.ownedResources()
    print "# o2 owns %s" % o2.ownedResources()

    print "# try getting exclusive resource"
    for i in range(3):
        p = "ex-domain-" + str(i) * 3
        add_res(o2, "domains", p, LockType.exclusive, 0)

    print "# o2 owns %s" % o2.ownedResources()

    print "# try getting locked resource - non blocking"
    acquired = add_res(o1, "domains", "ex-domain-111", LockType.exclusive, 0)

    if acquired:
        raise LException("double acquire: o1 domains.ex-domain-111 - non blocking")

    print "# try getting locked resource - sleep 3 sec"

    start = time.time()
    acquired = add_res(o1, "domains", "ex-domain-111", LockType.exclusive, 3000)

    if acquired:
        raise LException("double acquire: o1 domains.ex-domain-111 - block 3")
    blocked = time.time() - start
    if blocked < 3:
        raise LException("acquire blocked for less time than required: %d" % blocked)

    print "# release from wrong owner"
    negative(release_res, o1, "domains", "ex-domain-111")

    print "# o2 release an exclusive resource - ex-domain-111"
    release_res(o2, "domains", "ex-domain-111")
    ns_report("domains")
    print "# o2 owns %s" % o2.ownedResources()

    print "# try again by other owner"
    acquired = add_res(o1, "domains", "ex-domain-111", LockType.exclusive, 3000)

    if not acquired:
        raise LException("can't acquire; o1 domains.ex-domain-111")

    ns_report("domains")
    print "# o1 owns %s" % o1.ownedResources()
    print "# o2 owns %s" % o2.ownedResources()

    print "# try again to block for 10 sec but now schedule other owner to release in 3 sec"
    threading.Timer(3, o1.release, ["domains", "ex-domain-111"]).start()
    start = time.time()
    acquired = add_res(o2, "domains", "ex-domain-111", LockType.exclusive, 10000)

    if not acquired:
        raise LException("can't acquire; o1 domains.ex-domain-111")

    blocked = time.time() - start
    if blocked > 4: # one second overhead
        raise LException("acquire blocked for longer time than required: %d" % blocked)

    ns_report("domains")

    negative(add_res, o2, "domains", "domain-111", LockType.exclusive, 10000)

    print "# o2 release shared and try get exclusive access on shared lock - none blocking"
    release_res(o2, "domains", "domain-111")
    print "# o2 owns %s" % o2.ownedResources()
    acquired = add_res(o2, "domains", "domain-111", LockType.exclusive, 0)

    if acquired:
        raise LException("acquire shared lock for exclusive; o2 domains.domain-111")

    print "# try get exclusive access on shared lock - block one sec"
    acquired = add_res(o2, "domains", "domain-111", LockType.exclusive, 1000)

    if acquired:
        raise LException("acquire shared lock for exclusive; o2 domains.domain-111")

    print "# retry get exclusive access on shared lock - owner releases after 2 sec"
    threading.Timer(2, o1.release, ["domains", "domain-111"]).start()
    acquired = add_res(o2, "domains", "domain-111", LockType.exclusive, 4000)

    if not acquired:
        raise LException("can't acquire ex (was shared); o2 domains.domain-111")
    o = []
    for s in range(10):
        o.append(Owner("owner-%s" % (str(s) * 3)))

    print "# band acquire: 2 ex, 4 shared, 2 ex 2 shared"
    threading.Timer(1, add_res, [o[0], "domains", "domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(1.5, add_res, [o[1], "domains", "domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(2, add_res, [o[2], "domains", "domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, add_res, [o[3], "domains", "domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, add_res, [o[4], "domains", "domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, add_res, [o[5], "domains", "domain-111", LockType.shared, 10000]).start()

    threading.Timer(3, add_res, [o[6], "domains", "domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(3.5, add_res, [o[7], "domains", "domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(4, add_res, [o[8], "domains", "domain-111", LockType.shared, 10000]).start()
    threading.Timer(4, add_res, [o[9], "domains", "domain-111", LockType.shared, 10000]).start()

    time.sleep(5)

    print "# o2 release"
    release_res(o2, "domains", "domain-111")

    checkResourceOwenership("domains", "domain-111", [o[0]])
    ns_report("domains")
    print "# o2 owns %s" % o2.ownedResources()

    print "# 000 release"
    release_res(o[0], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[1]])
    ns_report("domains")

    print "# 111 release"
    release_res(o[1], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[2], o[3], o[4], o[5]])
    ns_report("domains")

    print "# 444 release"
    release_res(o[4], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[2], o[3], o[5]])
    ns_report("domains")

    print "# 222 & 555 release"
    release_res(o[2], "domains", "domain-111")
    release_res(o[5], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[3]])
    ns_report("domains")

    print "# 333 release"
    release_res(o[3], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[6]])
    ns_report("domains")

    print "# 666 release"
    release_res(o[6], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[7]])
    ns_report("domains")

    print "# 777 release"
    release_res(o[7], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[8], o[9]])
    ns_report("domains")

    print "# 888 release"
    release_res(o[8], "domains", "domain-111")
    checkResourceOwenership("domains", "domain-111", [o[9]])

    ns_report("domains")
    # let all time threads finish
    time.sleep(3)

    print "# try to register shared to shared resource (domain-222)"
    register_res(o[0], "domains", "domain-222", LockType.shared)
    checkResourceOwenership("domains", "domain-222", [o[0]])

    print "# release and try again exclusive"
    release_res(o[0], "domains", "domain-222")
    print "owner %s: %s" %(str(o[0]), o[0].ownedResources())
    ns_report("domains")
    register_res(o[0], "domains", "domain-222", LockType.exclusive)
    negative(checkResourceOwenership, "domains", "domain-222", [o[0]])

    print "# register another owner as shared - see that it will not stave prev owner"
    register_res(o[1], "domains", "domain-222", LockType.shared)
    negative(checkResourceOwenership, "domains", "domain-222", [o[1]])

    print "# register another owner (222) as shared"
    register_res(o[2], "domains", "domain-222", LockType.shared)

    print "# register anothe owner (333) for exclusive"
    register_res(o[3], "domains", "domain-222", LockType.exclusive)

    print "# release the last shared and see that 000 gets it"
    release_res(o1, "domains", "domain-222")
    release_res(o2, "domains", "domain-222")
    checkResourceOwenership("domains", "domain-222", [o[0]])

    print "# release owner 000 and check that 111 & 222 get it"
    release_res(o[0], "domains", "domain-222")
    checkResourceOwenership, "domains", "domain-222", [o[1], o[2]]
    negative(checkResourceOwenership, "domains", "domain-222", [o[3]])

    print "# release 222 - 111 still owns it shared"
    release_res(o[2], "domains", "domain-222")
    checkResourceOwenership, "domains", "domain-222", [o[1]]

    print "# release 111 - 333 shold get it"
    release_res(o[1], "domains", "domain-222")
    checkResourceOwenership, "domains", "domain-222", [o[3]]

    print "# try to register on several resources"
    register_res(o[0], "domains", "domain-222", LockType.shared)
    register_res(o[0], "domains", "domain-000", LockType.exclusive)
    register_res(o[0], "domains", "ex-domain-000", LockType.exclusive)
    register_res(o[0], "domains", "domain-111", LockType.shared)

    print "resources for 000: %s" % o[0].requestedResources()
    print "000 requestsGranted: %s" % o[0].requestsGranted()
    negative(checkResourceOwenership, "domains", "domain-222", [o[0]])
    negative(checkResourceOwenership, "domains", "domain-000", [o[0]])
    negative(checkResourceOwenership, "domains", "ex-domain-000", [o[0]])
    checkResourceOwenership("domains", "domain-111", [o[0]])

    print "lets some ownes release and check again"
    release_res(o[9], "domains", "domain-111")
    release_res(o1, "domains", "domain-000")
    release_res(o[3], "domains", "domain-222")
    checkResourceOwenership("domains", "domain-222", [o[0]])
    negative(checkResourceOwenership, "domains", "domain-000", [o[0]])
    negative(checkResourceOwenership, "domains", "ex-domain-000", [o[0]])
    checkResourceOwenership("domains", "domain-111", [o[0]])
    print "resources for 000: %s" % o[0].requestedResources()
    print "000 requestsGranted: %s" % o[0].requestsGranted()

    print "# use releaseAll on o2 to let 000 to get all requests"
    o2.releaseAll()
    check_not_owning(o2, "domains", "domain-111")
    checkResourceOwenership("domains", "domain-222", [o[0]])
    checkResourceOwenership("domains", "domain-000", [o[0]])
    checkResourceOwenership("domains", "ex-domain-000", [o[0]])
    checkResourceOwenership("domains", "domain-111", [o[0]])
    print "resources for 000: %s" % o[0].requestedResources()
    print "000 requestsGranted: %s" % o[0].requestsGranted()

    ns_report("domains")

    print "# mix acquire and register"

    add_res(o1, "domains", "ex-domain-111", LockType.exclusive, 0)

    lock_granted = []
    def sync_lock(o, ns, name, ltype, to):
        if add_res(o, ns, name, ltype, to):
            lock_granted.append(o)
            time.sleep(.5)
            release_res(o, ns, name)
            print "sync_lock: %s: locked and released %s/%s - %s" % (str(o), ns, name, ltype)
            return
        print "sync_lock: %s: not granted %s/%s" % (str(o), ns, name)

    def async_lock(o, ns, name, ltype, to):
        register_res(o, ns, name, ltype)
        start = time.time()
        while time.time() - start < (float(to)/1000):
            time.sleep(.5)
            if not o.requestsGranted():
                continue
            try:
                checkResourceOwenership(ns, name, [o])
            except:
                print "async_lock: %s: not granted %s/%s - cancled?" % (str(o), ns, name)
                return
            lock_granted.append(o)
            time.sleep(.5)
            release_res(o, ns, name)
            print "async_lock: %s: locked and released %s/%s- %s" % (str(o), ns, name, ltype)
            return
        print "async_lock: %s: not granted %s/%s" % (str(o), ns, name)

    def async_lock_wait(o, ns, name, ltype, to):
        register_res(o, ns, name, ltype)
        if o.wait(ns, name, to):
            lock_granted.append(o)
            time.sleep(.5)
            release_res(o, ns, name)
            print "async_lock_wait: %s: locked and released %s/%s - %s" % (str(o), ns, name, ltype)
            return
        print "async_lock_wait: %s: not granted %s/%s" % (str(o), ns, name)

    threading.Timer(1, sync_lock, [o[0], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(1.5, async_lock, [o[1], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(2, sync_lock, [o[2], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, async_lock_wait, [o[3], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, sync_lock, [o[4], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, async_lock_wait, [o[5], "domains", "ex-domain-111", LockType.shared, 10000]).start()

    threading.Timer(3, sync_lock, [o[6], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(3.5, async_lock, [o[7], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(4, async_lock_wait, [o[8], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(4, sync_lock, [o[9], "domains", "ex-domain-111", LockType.shared, 10000]).start()

    time.sleep(5)

    print "# start the race"
    o1.releaseAll()

    time.sleep(10)

    def check_granted_list(granted, order):
        i = 0
        for g in order:
            for o in g:
                if not int(str(granted[i])[-1:]) in g:
                    raise Exception("order not correct (#%s) %s - %s" % (i, [str(s) for s in granted], order))
                i += 1

    check_granted_list(lock_granted, [[0], [1], [2,3,4,5], [6], [7], [8,9]])
    ns_report("domains")

    print "# check sync cancel"
    add_res(o1, "domains", "ex-domain-111", LockType.exclusive, 0)
    register_res(o[0], "domains", "ex-domain-111", LockType.exclusive)

    threading.Timer(2, o2.cancel, ["domains", "ex-domain-111"]).start()
    start = time.time()
    granted = add_res(o2, "domains", "ex-domain-111", LockType.exclusive, 5000)
    if granted:
        raise Exception("granted even though it is canceled %s - domains/ex-domain-111" % str(o2))
    if time.time() - start >= 5.0:
        raise Exception("cancel didn't break wait %s - domains/ex-domain-111" % str(o2))

    release_res(o1, "domains", "ex-domain-111")
    checkResourceOwenership("domains", "ex-domain-111", [o[0]])

    print "# check async cancel"
    register_res(o2, "domains", "ex-domain-111", LockType.shared)
    register_res(o1, "domains", "ex-domain-111", LockType.shared)
    o2.cancel("domains", "ex-domain-111")
    release_res(o[0], "domains", "ex-domain-111")
    checkResourceOwenership("domains", "ex-domain-111", [o1])
    negative(checkResourceOwenership, "domains", "ex-domain-111", [o2])

    ns_report("domains")

    print "# mix acquire and register and cancel"
    lock_granted = []
    threading.Timer(1, sync_lock, [o[0], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(1.5, async_lock, [o[1], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(2, sync_lock, [o[2], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, async_lock_wait, [o[3], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, sync_lock, [o[4], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(2, async_lock_wait, [o[5], "domains", "ex-domain-111", LockType.shared, 10000]).start()

    threading.Timer(3, sync_lock, [o[6], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()
    threading.Timer(3.5, async_lock, [o[7], "domains", "ex-domain-111", LockType.exclusive, 10000]).start()

    threading.Timer(4, async_lock_wait, [o[8], "domains", "ex-domain-111", LockType.shared, 10000]).start()
    threading.Timer(4, sync_lock, [o[9], "domains", "ex-domain-111", LockType.shared, 10000]).start()

    time.sleep(5)
    o[1].cancel("domains", "ex-domain-111")
    o[3].cancel("domains", "ex-domain-111")
    o[6].cancel("domains", "ex-domain-111")
    o[9].cancel("domains", "ex-domain-111")

    print "# start the race"
    o1.releaseAll()

    time.sleep(10)
    check_granted_list(lock_granted, [[0], [2,4,5], [7], [8]])
    ns_report("domains")

    print "Done."
